﻿/**
 * CJMath
 *
 * @author		Allen Chou
 * @version		1.0.2 (last update: Dec 3 2008)
 * @link        http://cjcat.blogspot.com
 * @link		http://cjcat2266.deviantart.com
 */

package idv.cjcat{
	import flash.errors.IllegalOperationError;

	/**
	 * The <code>CJMath</code> class contains some useful mathematical functions.
	 */
	public class CJMath {
		
		//mathematical shortcuts
		//------------------------------------------------------------------------------------------------
		/**
		 * Clamps a value between bounds.
		 * 
		 * <listing>
		 * import idv.cjcat.CJMath;
		 * trace(CJMath.clamp(5, 3, 7));  //outputs 5
		 * trace(CJMath.clamp(1, 3, 7));  //outputs 3
		 * trace(CJMath.clamp(9, 3, 7));  //outputs 7</listing>
		 * 
		 * @param	input       Original value.
		 * @param	lowerBound  Lower bound.
		 * @param	upperBound  Upper bound.
		 * @return The original value clamped between two bounds.
		 */
		public static function clamp(input:Number, lowerBound:Number, upperBound:Number):Number {
			if (lowerBound > upperBound) throw new IllegalOperationError("the lowerBound must be less than the upperBound.");
			if (input < lowerBound) return lowerBound;
			if (input > upperBound) return upperBound;
			return input;
		}
		
		/**
		 * Interpolates between/beyond (x1, y1) and (x2, y2).
		 * @param	x1  First X value.
		 * @param	y1  First Y value.
		 * @param	x2  Second X value.
		 * @param	y2  Second Y value.
		 * @param	x3  Third X value.
		 * @param	usePercentage Whether x3 is absolute value or relative value.
		 * @return Third Y value.
		 */
		public static function interpolate(x1:Number, y1:Number, x2:Number, y2:Number, x3:Number, usePercentage:Boolean = false):Number {
			if (x1 == x2) throw new IllegalOperationError("x1 must not equal to x2.");
			if (!usePercentage) return y1 - ((y1 - y2) * (x1 - x3) / (x1 - x2));
			return y1 - ((y1 - y2) * x3);
		}
		
		/**
		 * Interpolates between (x1, y1) and (x2, y2). Same as interpolate(), only that the third Y value is looped between y1 and y2.
		 * @param	x1  First X value.
		 * @param	y1  First Y value.
		 * @param	x2  Second X value.
		 * @param	y2  Second Y value.
		 * @param	x3  Third X value.
		 * @param	usePercentage Whether x3 is absolute value or relative value.
		 * @return Third Y value.
		 */
		public static function linearLoop(x1:Number, y1:Number, x2:Number, y2:Number, x3:Number, usePercentage:Boolean = false):Number {
			var remainder:Number = x3 % (x2 - x1);
			if (x2 < x1) remainder *= -1;
			return interpolate(x1, y1, x2, y2, x1 + remainder, usePercentage);
		}
		
		/**
		 * Sets the decimal         precision of a Number.
		 * @param	value           Original value.
		 * @param	digit           How many digits of precision.
		 * @param	includeInteger  Whether to count integer part as digits of precision.
		 * @return Number with specified precision.
		 */
		public static function setPrecision(value:Number, digit:int, includeInteger:Boolean = false):Number {
			if (!includeInteger) {
				value *= Math.pow(10, digit);
				value = Math.round(value);
				value /= Math.pow(10, digit);
				return value;
			} else {
				var log:Number = Math.ceil(Math.log(value) / Math.log(10));
				value /= Math.pow(10, log);
				value *= Math.pow(10, digit)
				value  = Math.round(value);
				value /= Math.pow(10, digit);
				value *= Math.pow(10, log);
				return value;
			}
		}
		
		/**
		 * Checks if a value is the lower and upper bounds.
		 * @param	value    Value to be checked
		 * @param	min      Lower bound.
		 * @param	max      Higher bound.
		 * @param	openMin  Whether the lower bound is open.
		 * @param	openMax  Whether the upper bound is open.
		 * @return Whether the value is between the lower and upper bounds.
		 */
		public static function between(value:Number, min:Number, max:Number, openMin:Boolean = false, openMax:Boolean = false):Boolean {
			if (min > max) throw new IllegalOperationError("min must be less than max.");
			if ((openMin?(value > min):(value >= min) && (openMax?(value < max):(value <= max)))) {
				return true;
			} else {
				return false;
			}
		}
		
		/**
		 * Sine function with more parameters.
		 * @param	phase   Phase of the sine function.
		 * @param	radius  Amplitude of the sine function.
		 * @param	offset  Center of the sine function.
		 * @return R * sin(phase) + offset.
		 */
		public static function sin(phase:Number, radius:Number = 1, offset:Number = 0):Number {
			return radius * Math.sin(phase) + offset;
		}
		
		/**
		 * Cosine function with more parameters.
		 * @param	phase   Phase of the Cosine function.
		 * @param	radius  Amplitude of the sine function.
		 * @param	offset  Center of the sine function.
		 * @return R * sin(phase) + offset.
		 */
		public static function cos(phase:Number, radius:Number = 1, offset:Number = 0):Number {
			return radius * Math.cos(phase) + offset;
		}
		
		//end of mathematical shortcuts
		//------------------------------------------------------------------------------------------------
		
		
		//probability functions
		//------------------------------------------------------------------------------------------------
		
		/**
		 * 
		 * @param	max  The maximum value of the random integer.
		 * @return A random integer between 0 and max.
		 */
		public static function randomInt(max:uint):int {
			return Math.floor(Math.random() * (max + 1));
		}
		
		/**
		 * Simulates Gaussian sample distribution.
		 * @param	mean       Mean value of sample.
		 * @param	deviation  Deviation of sample.
		 * @param	iterations Iterations of simulation.
		 * @return A Gaussian random number.
		 */
		public static function pseudoGaussian(mean:Number, deviation:Number, iterations:int = 3):Number {
			if (iterations <= 0) return mean;
			var temp:Number = 0;
			for (var i:int = 1; i <= iterations; i++) {
				temp += (Math.random() - 0.5) * 2 * deviation;
			}
			temp /= iterations;
			temp += mean;
			return temp;
		}
		
		/**
		 * Evenly distributed sample.
		 * @param	mean        Mean value of sapmle
		 * @param	deviation   Deviation of sample
		 * @param	usePercent  Whether deviation is absolute or relative (percentage).
		 * @return An evenly distributed random number between (mean - deviation) and (mean + deviation).
		 */
		public static function uniformSample(mean:Number, deviation:Number, usePercent:Boolean = true):Number {
			if (usePercent) return mean + (Math.random() - 0.5) * deviation * 2 * mean;
			return mean + (Math.random() - 0.5) * deviation * 2;
		}
		
		/**
		 * Returns a Boolean value, with a probability equal to true.
		 * @param	p  The true probability. This value should lies between 0 and 1.
		 * @return The Boolean value.
		 */
		public static function prob(p:Number):Boolean {
			p = clamp(p, 0, 1);
			if (Math.random() <= p) return true;
			return false;
		}
		
		//end of probability functions
		//------------------------------------------------------------------------------------------------
	}
}